package lk.icta.common.dao.impl;

import lk.icta.common.AppConstants;
import lk.icta.common.ErrorCodes;
import lk.icta.common.dao.DSDivisionDAO;
import lk.icta.common.dao.PreloadableDAO;
import lk.icta.common.entity.DSDivision;
import lk.icta.common.entity.District;
import lk.icta.user.entity.User;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.Query;
import java.util.*;

/**
 * @author Supun Nimesh Karunathilaka
 */
public class DSDivisionDAOImpl extends BaseDAO implements DSDivisionDAO, PreloadableDAO {
    // direct cache of objects by PK - dsDivisionUKey
    private final Map<Integer, DSDivision> dsDivisionsByPK = new HashMap<Integer, DSDivision>();
    // local caches indexed by districtUKey and dsDivisionUKey
    private final Map<Integer, Map<Integer, String>> siNames = new HashMap<Integer, Map<Integer, String>>();
    private final Map<Integer, Map<Integer, String>> enNames = new HashMap<Integer, Map<Integer, String>>();
    private final Map<Integer, Map<Integer, String>> taNames = new HashMap<Integer, Map<Integer, String>>();

    @Transactional(propagation = Propagation.MANDATORY)
    public void addDSDivision(DSDivision dsDivision, User user) {
        em.persist(dsDivision);
    }

    @Transactional(propagation = Propagation.MANDATORY)
    public void updateDSDivision(DSDivision dsDivision, User user) {
        em.merge(dsDivision);
    }

    public Map<Integer, String> getDSDivisionNamesByDistrictId(int districtId, String language, User user) {
        Map<Integer, String> result = null;
        if (AppConstants.SINHALA.equals(language)) {
            result = getDsDivisionNamesImpl(siNames, districtId);
        } else if (AppConstants.ENGLISH.equals(language)) {
            result = getDsDivisionNamesImpl(enNames, districtId);
        } else if (AppConstants.TAMIL.equals(language)) {
            result = getDsDivisionNamesImpl(taNames, districtId);
        } else {
            handleException("Unsupported language : " + language, ErrorCodes.INVALID_LANGUAGE);
        }
        return result;
    }

     public Map<Integer, String> getDSDivisionNamesByDistrictId(int districtId, String language) {
        Map<Integer, String> result = null;
        if (AppConstants.SINHALA.equals(language)) {
            result = getDsDivisionNamesImpl(siNames, districtId);
        } else if (AppConstants.ENGLISH.equals(language)) {
            result = getDsDivisionNamesImpl(enNames, districtId);
        } else if (AppConstants.TAMIL.equals(language)) {
            result = getDsDivisionNamesImpl(taNames, districtId);
        } else {
            handleException("Unsupported language : " + language, ErrorCodes.INVALID_LANGUAGE);
        }
        return result;
    }

    private Map<Integer, String> getDsDivisionNamesImpl(
            Map<Integer, Map<Integer, String>> namesByDistrict, int districtId) {

        Map<Integer, String> dsDivisionNames = namesByDistrict.get(districtId);
        if (dsDivisionNames != null) {
            return dsDivisionNames;
        }
        logger.warn("No DS Divisions found for District : {}", districtId);
        return Collections.emptyMap();
    }

    public DSDivision getDSDivisionByPK(int dsDivisionUKey) {
        return dsDivisionsByPK.get(dsDivisionUKey);
    }

    public Set<DSDivision> getDSDivisionListByProvinceId(int provinceId) {
        Set<DSDivision> dsList = new HashSet<DSDivision>();
        //Iterator<DSDivision> it =  dsDivisionsByPK.values().iterator();

        for (Map.Entry<Integer, DSDivision> entry : dsDivisionsByPK.entrySet()){
              DSDivision ds = entry.getValue();
            logger.debug("ds name --------------------- {} ", ds.getEnDivisionName());
              if (ds.getDistrict().getProvince().getId() == provinceId) {
                  dsList.add(ds);
              }
        }

      /*  while (it.hasNext()) {
            DSDivision ds = it.next();
            logger.debug("ds name --------------------- {} ", ds.getEnDivisionName());
              if (ds.getDistrict().getProvince().getId() == provinceId) {
                  dsList.add(ds);
              }
        }*/
        return dsList;
    }

    public Set<DSDivision> getAllDSDivisions() {
        Set<DSDivision> dsList = new HashSet<DSDivision>();

        for (Map.Entry<Integer, DSDivision> entry : dsDivisionsByPK.entrySet()){
              DSDivision ds = entry.getValue();
             dsList.add(ds);

        }

        return dsList;
    }

    //TODO: to be removed
    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public List<DSDivision> findAll() {
        Query q = em.createNamedQuery("findAllDSDivisions");
        return q.getResultList();
    }

    public List<DSDivision> getDSDivisionByCode(int dsDivisionId, District district) {
        Query q = em.createNamedQuery("get.dsDivision.by.code");
        q.setParameter("dsDivisionId", dsDivisionId);
        q.setParameter("district", district);
        return q.getResultList();
    }

    public List<DSDivision> getDSDivisionByAnyName(DSDivision dsDivision) {
        Query q = em.createNamedQuery("get.dsDivision.by.anyName");
        q.setParameter("siName", dsDivision.getSiDivisionName());
        q.setParameter("enName", dsDivision.getEnDivisionName());
        q.setParameter("taName", dsDivision.getTaDivisionName());
        return q.getResultList();
    }

    /**
     * Loads all values from the database table into a cache
     */
    @Transactional(propagation = Propagation.NEVER, readOnly = true)
    public void preload() {

        Query query = em.createQuery("SELECT d FROM DSDivision d order by d.enDivisionName");
        List<DSDivision> results = query.getResultList();

        // Map<Integer, String> subMap = null;

        for (DSDivision d : results) {
            updateCache(d);
        }

        logger.debug("Loaded : {} DSDivisions from the database", results.size());
    }

    private void updateCache(DSDivision d) {
        Map<Integer, String> subMap;
        int districtId = d.getDistrict().getId();
        int dsDivisionCode = d.getCode();
        int dsDivisionId = d.getId();

        dsDivisionsByPK.put(d.getId(), d);

        subMap = siNames.get(districtId);
        if (subMap == null) {
            subMap = new LinkedHashMap<Integer, String>();
            siNames.put(districtId, subMap);
        }
        subMap.put(dsDivisionId, dsDivisionCode + " : " + d.getSiDivisionName());

        subMap = enNames.get(districtId);
        if (subMap == null) {
            subMap = new LinkedHashMap<Integer, String>();
            enNames.put(districtId, subMap);
        }
        subMap.put(dsDivisionId, dsDivisionCode + " : " + d.getEnDivisionName());

        subMap = taNames.get(districtId);
        if (subMap == null) {
            subMap = new LinkedHashMap<Integer, String>();
            taNames.put(districtId, subMap);
        }
        subMap.put(dsDivisionId, dsDivisionCode + " : " + d.getTaDivisionName());
    }
}
